/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.rbac.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.star.framework.core.IdEntity;
import cn.star.framework.entity.AbstractIdEntity;
import cn.star.framework.rbac.core.IRbacRole;
import cn.star.framework.rbac.core.IRbacUser;
import cn.star.framework.rbac.entity.Permission;
import cn.star.framework.rbac.entity.Resource;
import cn.star.framework.rbac.entity.RoleResource;
import cn.star.framework.rbac.entity.UserRole;
import cn.star.framework.rbac.repository.RoleResourceRepository;
import cn.star.framework.rbac.repository.UserRoleRepository;
import cn.star.framework.rbac.service.AuthorizationService;
import cn.star.framework.rbac.service.IRbacResourceService;
import cn.star.framework.rbac.service.PermissionService;
import cn.star.framework.rbac.type.ResourceType;
import cn.star.framework.rbac.util.ResourceServiceHolder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.criteria.CriteriaBuilder.In;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * {@link AuthorizationService}<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
@Slf4j
@Service
public class AuthorizationServiceImpl implements AuthorizationService {
  @Autowired private UserRoleRepository userRoleService;
  @Autowired private RoleResourceRepository roleResourceService;
  @Autowired private PermissionService permissionService;

  protected void validate(IdEntity<String> entity) {
    String type =
        entity == null ? IdEntity.class.getSimpleName() : entity.getClass().getSimpleName();

    Assert.isFalse(
        entity == null || StrUtil.isBlank(entity.getId()),
        "{} or primary key cannot be empty",
        type);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public <User extends IRbacUser<Group>, Group extends IRbacRole<Group>> void setUserGroups(
      User user, List<Group> roles) {
    this.validate(user);
    String userId = user.getId();

    List<UserRole> list = userRoleService.findByUserIdEquals(userId);
    userRoleService.deleteAllInBatch(list);

    List<UserRole> userRoles =
        roles.stream()
            .map(
                role -> {
                  UserRole item = new UserRole();
                  item.setUserId(userId);
                  item.setRoleId(role.getId());

                  return item;
                })
            .collect(Collectors.toList());

    userRoleService.saveAll(userRoles);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public <Group extends IRbacRole<Group>, User extends IRbacUser<Group>> void setGroupUsers(
      Group role, List<User> users) {
    this.validate(role);
    String roleId = role.getId();

    List<UserRole> list = userRoleService.findByRoleIdEquals(roleId);
    userRoleService.deleteAllInBatch(list);

    List<UserRole> userRoles =
        users.stream()
            .map(
                user -> {
                  UserRole item = new UserRole();
                  item.setRoleId(roleId);
                  item.setUserId(user.getId());

                  return item;
                })
            .collect(Collectors.toList());

    userRoleService.saveAll(userRoles);
  }

  private List<String> getAuthPermissionIds(List<Permission> permissions) {
    return CollectionUtil.isEmpty(permissions)
        ? CollectionUtil.newArrayList()
        : permissions.stream()
            .filter(Permission::isAuth)
            .map(AbstractIdEntity::getId)
            .collect(Collectors.toList());
  }

  protected <T extends Resource<T>> void setRoleResources(
      String roleId, List<T> resources, List<RoleResource> list) {
    for (T resource : resources) {
      RoleResource x =
          list.stream()
              .filter(
                  i -> roleId.equals(i.getRoleId()) && resource.getId().equals(i.getResourceId()))
              .findFirst()
              .orElse(null);
      List<String> permissionIds = getAuthPermissionIds(resource.getPermissions());
      if (x == null) {
        x = new RoleResource();
        x.setRoleId(roleId);
        x.setResourceId(resource.getId());
        x.setResourceType(resource.getType());
        x.setPermissionIds(permissionIds);
        list.add(x);
      } else {
        x.setPermissionIds(permissionIds);
      }
      if (CollectionUtil.isNotEmpty(resource.getChildren())) {
        this.setRoleResources(roleId, resource.getChildren(), list);
      }
    }
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public <Group extends IRbacRole<Group>, T extends Resource<T>> void setGroupResources(
      Group role, List<T> resources) {
    this.validate(role);
    String roleId = role.getId();
    List<RoleResource> list = roleResourceService.findByRoleIdEquals(roleId);
    this.setRoleResources(roleId, resources, list);
    roleResourceService.saveAll(list);
  }

  @Override
  public <T extends Resource<T>, Group extends IRbacRole<Group>> void setResourceGroups(
      T resource, List<Group> roles) {
    this.validate(resource);
    String resourceId = resource.getId();
    List<RoleResource> list = roleResourceService.findByResourceIdEquals(resourceId);
    for (Group role : roles) {
      this.setRoleResources(role.getId(), CollectionUtil.newArrayList(resource), list);
    }
    roleResourceService.saveAll(list);
  }

  protected <T extends Resource<T>> IRbacResourceService<T> getResourceService(ResourceType type) {
    try {
      return ResourceServiceHolder.get(type);
    } catch (Exception e) {
      log.debug("ignore exception, {}", e.getMessage());
      return null;
    }
  }

  @Override
  public <T extends Resource<T>> List<T> getGroupsAuthorizationResources(
      ResourceType type, List<String> roleIds) {
    IRbacResourceService<T> service = this.getResourceService(type);
    if (service == null) {
      return Collections.emptyList();
    }

    List<RoleResource> roleResources = roleResourceService.findByRoleIdIn(roleIds);
    Set<String> resourceIds =
        roleResources.stream().map(RoleResource::getResourceId).collect(Collectors.toSet());

    List<T> list = service.findAllByIds(resourceIds);

    if (CollectionUtil.isNotEmpty(list)) {
      List<Permission> permissions = this.getPermissionForResourceTypes(type);
      for (T resource : list) {
        List<Permission> ps = new ArrayList<>();
        RoleResource r =
            roleResources.stream()
                .filter(i -> resource.getId().equals(i.getResourceId()))
                .findFirst()
                .orElse(null);
        List<String> permissionIds = r == null ? Collections.emptyList() : r.getPermissionIds();
        for (Permission x : permissions) {
          if (resource.getType() == x.getResourceType()) {
            Permission p = new Permission();
            p.setId(x.getId());
            p.setCode(x.getCode());
            p.setName(x.getName());
            p.setDescribe(x.getDescribe());
            p.setAuth(permissionIds.contains(p.getId()));
            p.setResourceId(resource.getId());

            ps.add(p);
          }
        }

        resource.setPermissions(ps);
      }
    }

    return list;
  }

  @Override
  public Set<String> getUserAuthorizationGroupIds(String userId) {
    List<UserRole> userRoles = userRoleService.findByUserIdEquals(userId);

    return userRoles.stream().map(UserRole::getRoleId).collect(Collectors.toSet());
  }

  /**
   * 资源树权限计算
   *
   * @param trees 资源树
   * @param authorizations 授权的资源
   * @param permissions 权限
   */
  @SuppressWarnings({"unchecked", "rawtypes"})
  private void wrapper(
      List<Resource> trees, List<Resource> authorizations, List<Permission> permissions) {
    if (CollectionUtil.isEmpty(trees)) {
      return;
    }
    for (Resource x : trees) {
      ResourceType type = x.getType();
      Resource resource =
          authorizations.stream().filter(i -> i.getId().equals(x.getId())).findFirst().orElse(null);
      List<Permission> ps =
          permissions.stream()
              .filter(i -> i.getResourceType() == type)
              .collect(Collectors.toList());
      List<Permission> xs = resource == null ? ps : resource.getPermissions();
      xs.forEach(p -> p.setResourceId(x.getId()));
      x.setPermissions(xs);
      this.wrapper(x.getChildren(), authorizations, permissions);
    }
  }

  private List<Permission> getPermissionForResourceTypes(ResourceType type) {
    return permissionService.findAll(
        (root, query, cb) -> {
          In<ResourceType> in = cb.in(root.get("resourceType"));
          type.getResourceTypes().forEach(in::value);

          return in;
        });
  }

  @Override
  @SuppressWarnings({"unchecked", "rawtypes"})
  public List<Resource> getUserAuthorizationResourceTrees(String userId, ResourceType type) {
    IRbacResourceService service = this.getResourceService(type);
    if (service == null) {
      return Collections.emptyList();
    }

    List<Resource> trees = service.findAllForTree();
    List<Resource> resources = this.getUserAuthorizationResources(type, userId);
    List<Permission> permissions = this.getPermissionForResourceTypes(type);

    this.wrapper(trees, resources, permissions);

    return trees;
  }

  @Override
  @SuppressWarnings({"unchecked", "rawtypes"})
  public List<Resource> getGroupAuthorizationResourceTrees(String roleId, ResourceType type) {
    IRbacResourceService service = this.getResourceService(type);
    if (service == null) {
      return Collections.emptyList();
    }

    List<Resource> trees = service.findAllForTree();
    List<Resource> resources = this.getGroupsAuthorizationResources(type, roleId);
    List<Permission> permissions = this.getPermissionForResourceTypes(type);

    this.wrapper(trees, resources, permissions);

    return trees;
  }
}
